package com.lhh.springcloud.controller;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.lhh.entity.Member;
import com.lhh.entity.Result;
import com.lhh.springcloud.service.MemberService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * @author 李洪豪
 * @Sno 20220301044
 */
@Slf4j
@RestController
public class MemberController {
    @Resource
    private MemberService memberService;
    private static int count = 0;

    @PostMapping("/member/save")
    //如果前端是以json格式发送数据，则使用@RequestBody接收
    //如果前端是普通表单方式提交，则不使用@RequestBody才会进行对象的封装
    public Result saveMember(@RequestBody Member member) {
        int affect = memberService.addMember(member);
        if (affect > 0) {
            return Result.success("添加会员成功 vipmember-service-nacos-provider-10004", affect);
        } else {
            return Result.error("4xx", "添加会员失败");
        }
    }

    //根据id查询会员信息
    @GetMapping("/member/query/{id}")
    public Result queryMemberById(@PathVariable("id") Long id) {
        Member member = memberService.queryMemberById(id);
        //模拟openfeign超时测试，默认超时为1s
//        try {
//            Thread.sleep(5000);
//        } catch (InterruptedException e) {
//            throw new RuntimeException(e);
//        }
        log.info("当前queryMemberById线程id为{}，时间为{}", Thread.currentThread().getId(),  new Date());
        if (member != null) {
            return Result.success("查询会员成功 vipmember-service-nacos-provider-10004", member);
        } else {
            return Result.error("4xx", "查询会员失败");
        }
    }
    //模拟关联的流量控制，当t2达到峰值时，访问t1会失败
    @GetMapping("/t1")
    public Result  test1() {
        return Result.success("t1操作成功");
    }

    @GetMapping("/t2")
    public Result  test2() throws InterruptedException {
//        TimeUnit.MILLISECONDS.sleep(1000);
        String formattedDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date());
        log.info("当前test2线程id为{}，时间为{}", Thread.currentThread().getId(),  formattedDate);
        return Result.success("t2操作成功");
    }

    //测试熔断降级-慢调用比例
    @GetMapping("/t3")
    public Result  test3() throws InterruptedException {
        TimeUnit.MILLISECONDS.sleep(200);
        String formattedDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date());
        log.info("当前test3线程id为{}，时间为{}", Thread.currentThread().getId(),  formattedDate);
        return Result.success("t3操作成功");
    }
    //测试熔断降级-异常比例
    @GetMapping("/t4")
    public Result  test4() throws InterruptedException {
        if(++count%2 == 0){
            System.out.println(3/0);
        }
        log.info("熔断降级[异常比例]执行t4线程id={}", Thread.currentThread().getId());
        return Result.success("t3操作成功");
    }
    //测试熔点降级-异常数
    //异常数是按照分钟来进行的，在一分钟内，当请求次数达到阈值并且异常数达到阈值，则进行熔断降级；
    //1.统计时间窗口（Statistical Time Window）：
    //默认为1分钟（60秒），在此时间段内统计异常请求的数量。
    //如果设置的时间窗口小于60秒，Sentinel依然以1分钟为实际统计周期。
    //2.恢复机制（Half-Open State）：
    //熔断时间结束后，进入探测恢复状态，允许部分请求通过，观察是否恢复正常。
    //若探测请求成功，则关闭熔断器；若失败，则重新开启熔断。
    @GetMapping("/t5")
    public Result  test5(){
        if (++count<=6){
            System.out.println(3/0);
        }
        log.info("熔断降级[异常数]执行t5线程id={}", Thread.currentThread().getId());
        return Result.success("t5操作成功");
    }

    //测试热点限流
    //解读@SentinelResource的含义,当该方法出现限流的时候，就会调用名为blockHandlerForNews的方法，就不走该方法了
    @GetMapping("/news")
    @SentinelResource(value = "news", blockHandler = "blockHandlerForNews")
    public Result test6(@RequestParam(value = "id", required = false) String id,
                        @RequestParam(value = "type", required = false) String type){
        return Result.success("返回新闻id为" +id + "从DB查询的数据返回成功...");
    }


    public Result blockHandlerForNews(String id, String type, BlockException ex){
        return Result.error("401","热点参数"+id +"被限流,请稍后再试...");
    }

    //测试自定义全局限流处理类
    @GetMapping("/t6")
    //1.@SentinelResource 中的 value 是资源名称，必须与 Sentinel 控制台或代码中配置的限流规则中的资源名完全一致。
    //2.blockHandler01为CustomGlobalBlockHandler自定义全局限流处理类中的某个方法的方法名
    //3.解读@SentinelResource的含义,当该方法出现限流的时候，就会调用CustomGlobalBlockHandler类中名为blockHandler01的方法，就不走该方法了
   //4.blockHandler01方法必须和原方法(t6)的参数列表一致，并且多一个 BlockException 类型的参数。
    //5. exceptionsToIgnore：当异常为NullPointerException时，就不走我们自定义的异常了(fallback)，走系统默认的
    //6.要使@SentinelResource 生效，t6方法必须是public的
    @SentinelResource(value = "t6",
            blockHandlerClass = CustomGlobalBlockHandler.class,
            blockHandler = "blockHandler01",
            fallbackClass = CustomFallbackHandler.class,
            fallback = "fallbackHandler02",
            exceptionsToIgnore = NullPointerException.class)
    public Result t6(){
        if (++count%5 == 0){
            throw new NullPointerException("count的值异常 count=" + count);
        }
        if(count%6 == 0){
            throw new RuntimeException("count的值异常 count=" + count);
        }
        log.info("当前t6线程id为{}", Thread.currentThread().getId());
        return Result.success("t6操作成功");
    }
}
